//===--- ArgumentSource.h - Abstracted source of an argument ----*- C++ -*-===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2017 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//
//
// A structure for holding an abstracted source of a call argument.
// It's either:
//
//  - an expression, yielding an l-value or r-value
//  - an RValue
//  - an LValue
//
//===----------------------------------------------------------------------===//

#ifndef POLARPHP_PIL_GEN_ARGUMENTSOURCE_H
#define POLARPHP_PIL_GEN_ARGUMENTSOURCE_H

#include "polarphp/basic/ExternalUnion.h"
#include "polarphp/pil/gen/RValue.h"
#include "polarphp/pil/gen/LValue.h"

namespace polar {
namespace lowering {
class Conversion;

/// A means of generating an argument.
///
/// This is useful as a way to pass values around without either:
///   - requiring them to have already been evaluated or
///   - requiring them to come from an identifiable expression.
///
/// Being able to propagate values is important because there are a
/// number of cases (involving, say, property accessors) where values
/// are implicitly or previously generated.  However, being able to
/// propagate Expr*s is also important because there are several kinds
/// of expressions (such as closures) which can be emitted more
/// efficiently with a known target abstraction level.
///
/// Because an ArgumentSource might contain an unevaluated expression,
/// care must be taken when dealing with multiple ArgumentSources to
/// preserve the original evaluation order of the program.  APIs
/// working with multiple ArgumentSources should document the order in
/// which they plan to evaluate them.
class ArgumentSource {
   enum class Kind : uint8_t {
      Invalid,
      RValue,
      LValue,
      Expr,
   };

   struct RValueStorage {
      RValue Value;
      PILLocation Loc;
   };
   struct LValueStorage {
      LValue Value;
      PILLocation Loc;
   };

   using StorageMembers =
   ExternalUnionMembers<void, RValueStorage, LValueStorage, Expr*>;

   static StorageMembers::Index getStorageIndexForKind(Kind kind) {
      switch (kind) {
         case Kind::Invalid: return StorageMembers::indexOf<void>();
         case Kind::RValue:
            return StorageMembers::indexOf<RValueStorage>();
         case Kind::LValue: return StorageMembers::indexOf<LValueStorage>();
         case Kind::Expr: return StorageMembers::indexOf<Expr*>();
      }
      llvm_unreachable("bad kind");
   }

   ExternalUnion<Kind, StorageMembers, getStorageIndexForKind> Storage;
   Kind StoredKind;

public:
   ArgumentSource() : StoredKind(Kind::Invalid) {}
   ArgumentSource(PILLocation loc, RValue &&value) : StoredKind(Kind::RValue) {
      Storage.emplaceAggregate<RValueStorage>(StoredKind, std::move(value), loc);
   }
   ArgumentSource(PILLocation loc, LValue &&value) : StoredKind(Kind::LValue) {
      Storage.emplaceAggregate<LValueStorage>(StoredKind, std::move(value), loc);
   }
   ArgumentSource(Expr *e) : StoredKind(Kind::Expr) {
      assert(e && "initializing ArgumentSource with null expression");
      Storage.emplace<Expr*>(StoredKind, e);
   }
   // Cannot be copied.
   ArgumentSource(const ArgumentSource &other) = delete;
   ArgumentSource &operator=(const ArgumentSource &other) = delete;

   // Can be moved.
   ArgumentSource(ArgumentSource &&other) : StoredKind(other.StoredKind) {
      Storage.moveConstruct(StoredKind, std::move(other.Storage));
   }

   ArgumentSource &operator=(ArgumentSource &&other) {
      Storage.moveAssign(StoredKind, other.StoredKind, std::move(other.Storage));
      StoredKind = other.StoredKind;
      other.Storage.destruct(other.StoredKind);
      other.StoredKind = Kind::Invalid;
      return *this;
   }

   ~ArgumentSource() {
      Storage.destruct(StoredKind);
   }

   explicit operator bool() const & {
      switch (StoredKind) {
         case Kind::Invalid:
            return false;
         case Kind::RValue:
            return !asKnownRValue().isNull();
         case Kind::LValue:
            return asKnownLValue().isValid();
         case Kind::Expr:
            return asKnownExpr() != nullptr;
      }
      llvm_unreachable("bad kind");
   }

   CanType getSubstRValueType() const & {
      switch (StoredKind) {
         case Kind::Invalid:
            llvm_unreachable("argument source is invalid");
         case Kind::RValue:
            return asKnownRValue().getType();
         case Kind::LValue:
            return asKnownLValue().getSubstFormalType();
         case Kind::Expr:
            return asKnownExpr()->getType()->getInOutObjectType()->getCanonicalType();
      }
      llvm_unreachable("bad kind");
   }

   bool hasLValueType() const & {
      switch (StoredKind) {
         case Kind::Invalid: llvm_unreachable("argument source is invalid");
         case Kind::RValue:
            return false;
         case Kind::LValue: return true;
         case Kind::Expr: return asKnownExpr()->isSemanticallyInOutExpr();
      }
      llvm_unreachable("bad kind");
   }

   PILLocation getLocation() const & {
      switch (StoredKind) {
         case Kind::Invalid:
            llvm_unreachable("argument source is invalid");
         case Kind::RValue:
            return getKnownRValueLocation();
         case Kind::LValue:
            return getKnownLValueLocation();
         case Kind::Expr:
            return asKnownExpr();
      }
      llvm_unreachable("bad kind");
   }

   bool isExpr() const & { return StoredKind == Kind::Expr; }
   bool isRValue() const & { return StoredKind == Kind::RValue; }
   bool isLValue() const & { return StoredKind == Kind::LValue; }

   /// Whether this argument is for a default argument that should be delayed.
   /// Note that this will return false for caller-side default arguments which
   /// are emitted directly.
   bool isDelayedDefaultArg() const {
      switch (StoredKind) {
         case Kind::Invalid:
            llvm_unreachable("argument source is invalid");
         case Kind::RValue:
         case Kind::LValue:
            return false;
         case Kind::Expr: {
            auto *defaultArg = dyn_cast<DefaultArgumentExpr>(asKnownExpr());
            if (!defaultArg)
               return false;
            return !defaultArg->isCallerSide();
         }
      }
      llvm_unreachable("bad kind");
   }

   /// Return the default argument owner and parameter index, consuming
   /// the argument source. Will assert if this is not a default argument.
   DefaultArgumentExpr *asKnownDefaultArg() && {
      return cast<DefaultArgumentExpr>(std::move(*this).asKnownExpr());
   }

   /// Given that this source is storing an RValue, extract and clear
   /// that value.
   RValue &&asKnownRValue(PILGenFunction &SGF) && {
      return std::move(Storage.get<RValueStorage>(StoredKind).Value);
   }
   const RValue &asKnownRValue() const & {
      return Storage.get<RValueStorage>(StoredKind).Value;
   }
   PILLocation getKnownRValueLocation() const & {
      return Storage.get<RValueStorage>(StoredKind).Loc;
   }

   /// Given that this source is storing an LValue, extract and clear
   /// that value.
   LValue &&asKnownLValue() && {
      return std::move(Storage.get<LValueStorage>(StoredKind).Value);
   }
   const LValue &asKnownLValue() const & {
      return Storage.get<LValueStorage>(StoredKind).Value;
   }
   PILLocation getKnownLValueLocation() const & {
      return Storage.get<LValueStorage>(StoredKind).Loc;
   }

   Expr *findStorageReferenceExprForBorrow() &&;

   /// Given that this source is an expression, extract and clear
   /// that expression.
   Expr *asKnownExpr() && {
      Expr *result = Storage.get<Expr*>(StoredKind);
      Storage.resetToEmpty<Expr*>(StoredKind, Kind::Invalid);
      StoredKind = Kind::Invalid;
      return result;
   }

   /// Return an unowned handle to the r-value stored in this source. Undefined
   /// if this ArgumentSource is not an rvalue.
   RValue &peekRValue() &;

   RValue getAsRValue(PILGenFunction &SGF, SGFContext C = SGFContext()) &&;
   ManagedValue getAsSingleValue(PILGenFunction &SGF,
                                 SGFContext C = SGFContext()) &&;
   ManagedValue getAsSingleValue(PILGenFunction &SGF,
                                 AbstractionPattern origFormalType,
                                 SGFContext C = SGFContext()) &&;

   ManagedValue getConverted(PILGenFunction &SGF, const Conversion &conversion,
                             SGFContext C = SGFContext()) &&;

   void forwardInto(PILGenFunction &SGF, Initialization *dest) &&;
   void forwardInto(PILGenFunction &SGF, AbstractionPattern origFormalType,
                    Initialization *dest, const TypeLowering &destTL) &&;

   /// If we have an rvalue, borrow the rvalue into a new ArgumentSource and
   /// return the ArgumentSource. Otherwise, assert.
   ArgumentSource borrow(PILGenFunction &SGF) const &;

   ManagedValue materialize(PILGenFunction &SGF) &&;

   /// Emit this value to memory so that it follows the abstraction
   /// patterns of the original formal type.
   ///
   /// \param expectedType - the lowering of getSubstRValueType() under the
   ///   abstractions of origFormalType
   ManagedValue materialize(PILGenFunction &SGF,
                            AbstractionPattern origFormalType,
                            PILType expectedType = PILType()) &&;

   bool isObviouslyEqual(const ArgumentSource &other) const;

   ArgumentSource copyForDiagnostics() const;

   void dump() const;
   void dump(raw_ostream &os, unsigned indent = 0) const;

private:
   /// Private helper constructor for delayed borrowed rvalues.
   ArgumentSource(PILLocation loc, RValue &&rv, Kind kind);

   // Make this non-move accessor private to make it more difficult
   // to accidentally re-emit values.
   Expr *asKnownExpr() const & {
      return Storage.get<Expr*>(StoredKind);
   }
};

class PreparedArguments {
   SmallVector<AnyFunctionType::Param, 8> Params;
   std::vector<ArgumentSource> Arguments;
   unsigned IsNull : 1;
public:
   PreparedArguments() : IsNull(true) {}
   explicit PreparedArguments(ArrayRef<AnyFunctionType::Param> params)
      : IsNull(true) {
      emplace(params);
   }

   // Decompse an argument list expression.
   PreparedArguments(ArrayRef<AnyFunctionType::Param> params, Expr *arg);

   // Move-only.
   PreparedArguments(const PreparedArguments &) = delete;
   PreparedArguments &operator=(const PreparedArguments &) = delete;

   PreparedArguments(PreparedArguments &&other)
      : Params(std::move(other.Params)), Arguments(std::move(other.Arguments)),
        IsNull(other.IsNull) {}
   PreparedArguments &operator=(PreparedArguments &&other) {
      Params = std::move(other.Params);
      Arguments = std::move(other.Arguments);
      IsNull = other.IsNull;
      other.IsNull = true;
      return *this;
   }

   /// Returns true if this is a null argument list.  Note that this always
   /// indicates the total absence of an argument list rather than the
   /// possible presence of an empty argument list.
   bool isNull() const { return IsNull; }

   /// Returns true if this is a non-null and completed argument list.
   bool isValid() const {
      assert(!isNull());
      return Arguments.size() == Params.size();
   }

   /// Return the formal type of this argument list.
   ArrayRef<AnyFunctionType::Param> getParams() const {
      assert(!isNull());
      return Params;
   }

   MutableArrayRef<ArgumentSource> getSources() && {
      assert(isValid());
      return Arguments;
   }

   /// Emplace a (probably incomplete) argument list.
   void emplace(ArrayRef<AnyFunctionType::Param> params) {
      assert(isNull());
      Params.append(params.begin(), params.end());
      IsNull = false;
   }

   /// Add an emitted r-value argument to this argument list.
   void add(PILLocation loc, RValue &&arg) {
      assert(!isNull());
      Arguments.emplace_back(loc, std::move(arg));
   }

   /// Add an arbitrary argument source to these arguments.
   ///
   /// An argument list with an arbtrary argument source can't generally
   /// be copied.
   void addArbitrary(ArgumentSource &&arg) {
      assert(!isNull());
      Arguments.emplace_back(std::move(arg));
   }

   /// Copy these prepared arguments.  This propagates null.
   PreparedArguments copy(PILGenFunction &SGF, PILLocation loc) const;

   bool isObviouslyEqual(const PreparedArguments &other) const;

   PreparedArguments copyForDiagnostics() const;
};

} // end namespace lowering
} // end namespace polar

#endif // POLARPHP_PIL_GEN_ARGUMENTSOURCE_H
